import channels_graphql_ws
import graphene

from graphql import GraphQLError
from .schema import ObjektoNode, ObjektoLigiloNode
from ..models import Objekto, ObjektoLigilo
from kosmo.models import KosmoStelsistemoKubo
from taskoj.api.schema import TaskojTaskoNode, TaskojProjektoNode
from taskoj.models import TaskojTasko
from universo_bazo.models import Realeco
from universo_uzantoj_websocket.models import UniversoUzantojWebsocket


# Реализация подписки для изменения действий объектов в кубе:
# - объект вошёл/вышел
# - изменилась задача у объекта:
#   - движение/остановка
#   - стрельба
# Объединение возвращаемых объектов действий
class ObjektoEventojUnion(graphene.Union):
    class Meta:
        # Типы объединения
        types = (
            TaskojTaskoNode,
            ObjektoNode,
        )


# Подписки для WebSocket на действия в кубе
class ObjektoEventoj(channels_graphql_ws.Subscription):
    """Подписка на действия в кубе"""
    evento = graphene.String(required=True)
    objekto = graphene.relay.node.Field(ObjektoNode)
    projekto = graphene.relay.node.Field(TaskojProjektoNode)
    tasko = graphene.relay.node.Field(TaskojTaskoNode)
    # вышестоящий объект, обладатель данного объекта
    posedi = graphene.relay.node.Field(ObjektoNode)
    # верхний объект владения всех связанных вниз объектов
    mastro = graphene.relay.node.Field(ObjektoNode)
    # procedo = graphene.Field(ObjektoEventojUnion)

    class Arguments:
        """Тут аргументы, передающиеся для подписки на события в кубе"""
        # ID куба
        kuboj = graphene.List(graphene.Int, required=True)
        realeco = graphene.Int(required=True)

    def subscribe(self, info, kuboj, realeco):
        """В этом методе определяется список кубов для подписки на действия в них"""
        uzanto = info.context.user
        if uzanto.is_authenticated:
            # просмотр разрешен всем зарегестрированным пользователям
            # реальность
            try:
                realeco_table = Realeco.objects.get(id=realeco, forigo=False,
                                                    arkivo=False, publikigo=True)
            except Realeco.DoesNotExist:
                raise GraphQLError(
                    'Невозможно отслеживать реальность c ID {}, она не сущствуют или не доступна'.format(
                        realeco
                    )
                )

            # проверяем, есть ли такой куб в системе
            kuboj_res = KosmoStelsistemoKubo.objects.filter(
                id__in=kuboj, 
                forigo=False,
                arkivo=False, publikigo=True)

            kuboj_set = set(
                kuboj_res.values_list('id', flat=True)
            )

            if not kuboj_res:
                raise GraphQLError(
                    'Невозможно отслеживать кубы c ID {}, они не сущствуют или не доступны'.format(
                        set(kuboj) - kuboj_set
                    )
                )

            # записываем информацию об установки подписки
            try:
                uzanto_websocket = UniversoUzantojWebsocket.objects.get(
                    posedanto = uzanto
                )
            except UniversoUzantojWebsocket.DoesNotExist:
                uzanto_websocket = UniversoUzantojWebsocket.objects.create(
                    posedanto = uzanto,
                )
            uzanto_websocket.kuboj = '{}'.format(kuboj_set)
            uzanto_websocket.realeco = realeco_table
            uzanto_websocket.subscription_kosmo = True
            uzanto_websocket.save()

            return list(map(lambda v: 'Kubo_{}_realeco_{}'.format(v, realeco), kuboj_set)) or None

        raise GraphQLError('Необходима авторизация')

    @staticmethod
    def publish(payload, info, kuboj, realeco):
        """ публикация изминений в кубе """
        evento = payload.get('evento')

        tasko = None
        objekto = None
        projekto = None
        posedi = None
        mastro = None
        try:
            if evento == 'kubo_tasko':
                tasko = TaskojTasko.objects.get(
                    uuid=payload.get('tasko'),
                    publikigo=True,
                    arkivo=False,
                    forigo=False
                )
                objekto=tasko.objekto
                projekto=tasko.projekto

            elif evento == 'kubo_objekto':
                objekto = Objekto.objects.get(
                    uuid=payload.get('objekto'),
                    publikigo=True,
                    arkivo=False,
                    forigo=False
                )
                if payload.get('posedi',False):
                    posedi = Objekto.objects.get(
                        uuid=payload.get('posedi'),
                        publikigo=True,
                        arkivo=False,
                        forigo=False
                    )
                if payload.get('mastro',False):
                    mastro = Objekto.objects.get(
                        uuid=payload.get('mastro'),
                        publikigo=True,
                        arkivo=False,
                        forigo=False
                    )

            # рассылаем всем, т.к. изменения могут касаться владельца объекта со стороны других объектов и со стороны сервера
            return ObjektoEventoj(evento=evento, objekto=objekto, projekto=projekto, tasko=tasko,
                posedi=posedi, mastro=mastro)
        except TaskojTasko.DoesNotExist:
            pass

        return ObjektoEventoj.SKIP

    @classmethod
    def movado_tasko(cls, kubo, tasko):
        """Этот метод должен вызываться при изминении движения объекта в кубе"""
        cls.broadcast(
            group='Kubo_{}_realeco_{}'.format(kubo.id, tasko.realeco.id),
            payload={
                'evento': 'kubo_tasko',
                'kubo': str(kubo.uuid),
                'tasko': str(tasko.uuid)
            }
        )

    @classmethod
    def kubo_objekto(cls, objekto, realeco, kubo, posedi, mastro):
        """Этот метод должен вызываться при изминении объекта в кубе. У объекта указателя на куб может не быть, если это составная часть"""
        group='Kubo_{}_realeco_{}'.format(kubo.id, realeco.id)
        payload={
            'evento': 'kubo_objekto',
            'kubo': str(kubo.uuid),
            'realeco': str(realeco.uuid),
            'objekto': str(objekto.uuid),
        }
        if posedi:
            payload['posedi'] = str(posedi.uuid)
        if mastro:
            payload['mastro'] = str(mastro.uuid)
        cls.broadcast(
            group=group,
            payload=payload
        )


# Подписки для WebSocket на связи с объектом (необходимо для выхода из станции при нахождении в станции)
class LigiloEventoj(channels_graphql_ws.Subscription):
    """Подписка на связи с объектом (необходимо для выхода из станции при нахождении в станции)"""
    evento = graphene.String(required=True)
    ligilo = graphene.relay.node.Field(ObjektoLigiloNode)

    class Arguments:
        """Тут аргументы, передающиеся для подписки на события в связи"""
        # UUID связи
        ligilo = graphene.String(required=True)

    def subscribe(self, info, ligilo):
        """В этом методе определяется список кубов для подписки на действия в них"""
        if info.context.user.is_authenticated:
            # просмотр разрешен всем зарегестрированным пользователям
            try:
                ligilo_table = ObjektoLigilo.objects.get(uuid=ligilo, forigo=False,
                                                    arkivo=False, publikigo=True)
            except ObjektoLigilo.DoesNotExist:
                raise GraphQLError(
                    'Невозможно отслеживать связь c UUID {}, она не сущствуют или не доступна'.format(
                        ligilo
                    )
                )

            ligilo_list = list()
            ligilo_list.append('Ligilo_{}'.format(ligilo))
            return ligilo_list or None

        raise GraphQLError('Необходима авторизация')

    @staticmethod
    def publish(payload, info, ligilo):
        """ публикация изминений в связи """
        evento = payload.get('evento')

        ligilo = None
        try:
            if evento == 'ligilo':
                ligilo = ObjektoLigilo.objects.get(
                    uuid=payload.get('ligilo'),
                    publikigo=True,
                    arkivo=False
                )

            # рассылаем всем, т.к. изменения могут касаться владельца объекта со стороны других объектов и со стороны сервера
            return LigiloEventoj(evento=evento, ligilo=ligilo)
        except ObjektoLigilo.DoesNotExist:
            pass

        return LigiloEventoj.SKIP

    @classmethod
    def sxangxi_ligilo(cls, ligilo):
        """Этот метод должен вызываться при изминении связи"""
        cls.broadcast(
            group='Ligilo_{}'.format(str(ligilo.uuid)),
            payload={
                'evento': 'ligilo',
                'ligilo': str(ligilo.uuid)
            }
        )


# Подписки для WebSocket на изменения в связях вложенных на любом уровне в указанный объект (необходимо при нахождении в станции)
class ObjektoLigiloEventoj(channels_graphql_ws.Subscription):
    """
    Подписка на изменения в связях вложенных на любом уровне в указанный объект (необходимо при нахождении в станции).
    Возвращаем изменённую связь.
    """
    evento = graphene.String(required=True)
    # ветка - инф. (серия сообщений и комментариев по какой-л. теме форума)  - fadeno
    fadeno_ligilo = graphene.relay.node.Field(ObjektoLigiloNode) # в какой ветке произошло изменение
    ligilo = graphene.relay.node.Field(ObjektoLigiloNode) # в какой конкретно связи произошло изменение
    objekto_sxangxi = graphene.relay.node.Field(ObjektoNode) # в каком объекте произошло изменение

    class Arguments:
        """Тут аргументы, передающиеся для подписки на события в объекте"""
        # UUID объекта
        objekto = graphene.String(required=True)

    def subscribe(self, info, objekto):
        """В этом методе определяется объект для подписки на изминения связей"""
        if info.context.user.is_authenticated:
            # просмотр разрешен всем зарегестрированным пользователям
            try:
                objekto_table = Objekto.objects.get(uuid=objekto, forigo=False,
                                                    arkivo=False, publikigo=True)
            except Objekto.DoesNotExist:
                raise GraphQLError(
                    'Невозможно отслеживать объект c UUID {} в связи с его отсутствием в системе'.format(
                        objekto
                    )
                )

            objekto_list = list()
            objekto_list.append('Objekto_{}'.format(objekto))
            return objekto_list or None

        raise GraphQLError('Необходима авторизация')

    @staticmethod
    def publish(payload, info, objekto):
        """ публикация изминений в связи """
        evento = payload.get('evento')

        fadeno_ligilo = None
        ligilo = None
        objekto_sxangxi = None
        try:
            if evento == 'objekto':
                fadeno_ligilo = ObjektoLigilo.objects.get(
                    uuid=payload.get('fadeno_ligilo'),
                    publikigo=True,
                    arkivo=False
                )
            if payload.get('ligilo',False):
                try:
                    ligilo = ObjektoLigilo.objects.get(
                        uuid=payload.get('ligilo'),
                        publikigo=True,
                        arkivo=False
                    )
                except ObjektoLigilo.DoesNotExist:
                    ligilo = None
            if payload.get('objekto_sxangxi',False):
                try:
                    objekto_sxangxi = Objekto.objects.get(
                        uuid=payload.get('objekto_sxangxi'),
                        publikigo=True,
                        arkivo=False
                    )
                except Objekto.DoesNotExist:
                    objekto_sxangxi = None
            
            # рассылаем всем, т.к. управление идёт через задачи
            return ObjektoLigiloEventoj(evento=evento, fadeno_ligilo=fadeno_ligilo, ligilo=ligilo, objekto_sxangxi=objekto_sxangxi)
        except ObjektoLigilo.DoesNotExist:
            pass

        return ObjektoLigiloEventoj.SKIP

    @classmethod
    def sxangxi_objekto_ligilo(cls, objekto, fadeno_ligilo, ligilo, objekto_sxangxi):
        """Этот метод должен вызываться при изминении связи у конкретного объекта"""
        payload={
            'evento': 'objekto',
            'objekto': str(objekto.uuid),
            'fadeno_ligilo': str(fadeno_ligilo.uuid),
        }
        if ligilo:
            payload['ligilo'] = str(ligilo.uuid)
        if objekto_sxangxi:
            payload['objekto_sxangxi'] = str(objekto_sxangxi.uuid)
        cls.broadcast(
            group='Objekto_{}'.format(str(objekto.uuid)),
            payload=payload
        )


class ObjektoSubscription(graphene.ObjectType):
    objekto_eventoj = ObjektoEventoj.Field()
    ligilo_eventoj = LigiloEventoj.Field()
    objekto_ligilo_eventoj = ObjektoLigiloEventoj.Field()
